#include "flops_job.h"
#include "boost/function.hpp"
#include "boost/bind.hpp"
#include "boost/type_traits.hpp"
#include "boost/static_assert.hpp"
#include "tbb/parallel_for.h"
#include "tbb/blocked_range.h" 
#include "tbb/parallel_invoke.h"
#include "tbb/mutex.h"
#include "tbb/enumerable_thread_specific.h"

#include <iostream>

#include <boost/context.hpp>
#include <dbgprint/dbgprint.hpp>

tbb::mutex cout_mu;

#define print_locked(msg)\
        {tbb::mutex::scoped_lock lock(cout_mu);  std::cout << msg << std::endl;}

#define print(msg)\
        std::cout << msg << std::endl;

# if 1
#       define suspend_me()\
                do { _cpi.get()->suspend(); } while (0)
# else    
#       define suspend_me()\
                do { ((sc_thread*)arg)->suspend(); } while (0)
# endif

int N;

class sc_thread;
typedef sc_thread* handle;
typedef tbb::enumerable_thread_specific<handle> handle_ets;

struct cpi{
        handle_ets _handle;
        void set(handle h){ print_locked("*** cpi=" << _handle.local() <<" set to h=" << h); _handle.local() = h; }
        handle get(){ return _handle.local(); }
        void invalidate(){ print_locked("*** cpi=" << _handle.local() <<" reset to 0");  _handle.local() = 0; }
};

cpi _cpi;
struct sc_thread
{
        boost::context  m_main;
        boost::context  m_active;

        template <class F>
        sc_thread(F f)
                : m_main(boost::context::current())
                , m_active(boost::context::create(f,m_main,this,boost::context::default_stacksize))
        {}

        void resume(){
                print_locked("\\\\resuming thread=" << this);
                _cpi.set(this);
                m_main.jump_to(m_active);
        }
        
        void suspend(){
                _cpi.invalidate();
                print_locked("//suspending thread=" << this);
                m_active.jump_to(m_main);
        }
};

#define WORKER(j,ch)\
void worker ## j (void* arg){                                                                   \
        print_locked("starting worker" #j);                                                     \
        int i=0;                                                                                \
        for(;;){                                                                                \
                if(!getenv("SILENT")){ pilot::flops_job(N,ch); } else { pilot::flops_job(N); }  \
                print_locked("i_" #j "="<< i++);                                                   \
                suspend_me();                                                                   \
        }                                                                                       \
}

WORKER(1,'#')
WORKER(2,'^')
WORKER(3,'!')

void inline par_invoke_clean(){
	boost::function<void (size_t,const char*)> f_c = static_cast<void (*)(size_t,const char*)>(::pilot::flops_job);
	boost::function<void ()> f1_c = boost::bind(f_c,N,"#");
	boost::function<void ()> f2_c = boost::bind(f_c,N,"^");
        print_locked("\\\\\\\\For comparison, clean par. invoke\\\\\\\\================================================");
        tbb::parallel_invoke(f1_c,f2_c);
        print_locked("////End clean par. invoke////==========================================");
}

//usage: ./a.out log_flops num_iter [is_clean_example]
int main(int argc, char** argv){
        N=atoi(argv[1]);
        size_t num_iter = atoi(argv[2]);
        if(argc>3 && atoi(argv[3])) par_invoke_clean();

        


        sc_thread t1(&worker1);
        sc_thread t2(&worker2);
        sc_thread t3(&worker3);

        print_locked("\\\\\\\\Proceeding to suspend-resume test, "<< num_iter <<" iterations\\\\\\\\");
        for(int j=0;j<num_iter;j++){
	       tbb::parallel_invoke(
                                 boost::bind(&sc_thread::resume,&t1)
                               , boost::bind(&sc_thread::resume,&t2)
                               , boost::bind(&sc_thread::resume,&t3)
                        );
                print_locked("-------- ---------- ---------");
        }
        print_locked("////End test////");
        return 0;
}
